package framework.fault;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;







public class FaultDetectionPeer extends UnicastRemoteObject implements IFaultDetectionPeer {
	private static final int HEART_BEAT_INTERVAL = 5000;
	private static final int HEART_BEAT_TIMEOUT = 10000;

	private Map<String,Timer> heartBeatTimeouts = 
		Collections.synchronizedMap(new HashMap<String,Timer>());
	private Map<String,Timer> heartBeatTimers = 
		Collections.synchronizedMap(new HashMap<String,Timer>());
	
	private String name;
	private Map<String,IFaultDetectionPeer> peers = 
		Collections.synchronizedMap(new HashMap<String,IFaultDetectionPeer>());
	
	private FaultTolerantPeer mediator;
	
	private volatile int failurePercentage = 0;
	private Random random = new Random();

	
	public static FaultDetectionPeer createInstance(FaultTolerantPeer mediator, String name, int failurePercentage)
	throws RemoteException {
		FaultDetectionPeer peer = new FaultDetectionPeer();
		peer.name = name;
		peer.mediator = mediator;
		peer.failurePercentage = failurePercentage;
		return peer;
	}

	public static void destroyInstance(FaultDetectionPeer peer) {
		peer.failurePercentage = 100;
		HashMap<String,IFaultDetectionPeer> otherPeers = 
			new HashMap<String,IFaultDetectionPeer>(peer.peers);
		for (String otherPeer: otherPeers.keySet()) {
			peer.removePeer(otherPeer);
		}
	}

	private FaultDetectionPeer() throws RemoteException {
		super();
	}

	//TODO timer is reset perfectly on both peers, so they will contact each other at same time
	//TODO revert back to old version without names
	
	public synchronized void addPeer(String name, IFaultDetectionPeer otherPeer) {
			this.peers.put(name,otherPeer);
			rescheduleHeartBeatTimer(name);
			rescheduleHeartBeatTimeout(name);
	}

	public synchronized void removePeer(String name) {
		cancelHeartBeatTimer(name);
		cancelHeartBeatTimeout(name);
		this.peers.remove(name);
	}

	private synchronized void cancelHeartBeatTimer(String otherPeer) {
		Timer heartBeatTimer = heartBeatTimers.get(otherPeer);
		if (heartBeatTimer!=null) {
			heartBeatTimer.cancel();
		}
	}
	private synchronized void rescheduleHeartBeatTimer(final String otherPeer) {
		cancelHeartBeatTimer(otherPeer);
		
		heartBeatTimers.put(otherPeer, new Timer());
		heartBeatTimers.get(otherPeer).schedule(new TimerTask() {
			@Override
			public void run() {
				try {
					IFaultDetectionPeer peer = FaultDetectionPeer.this.peers.get(otherPeer);
					if (peer!=null) {
						if (peer.heartBeat(FaultDetectionPeer.this.name) == true) {
//							System.out.println(new Date().getSeconds()+" "+
//									"<"+FaultDetectionPeer.this.name+
//									"> REPORT ONLINE "+otherPeer);
							FaultDetectionPeer.this.rescheduleHeartBeatTimer(otherPeer);
							FaultDetectionPeer.this.rescheduleHeartBeatTimeout(otherPeer);
						} else {
							FaultDetectionPeer.this.otherPeerCrashed(otherPeer);
						}
					} 

				} catch (RemoteException e) {
					//						FaultDetectionPeer.this.otherPeerCrashed(peer);
				}
				//this adds some random timing (0-2secs) in 100ms steps
				// in order to avoid both timers triggering at same time
			}}, HEART_BEAT_INTERVAL+FaultDetectionPeer.this.random.nextInt(20)*100);
	}

	private synchronized void cancelHeartBeatTimeout(String otherPeer) {
		Timer heartBeatTimeout= heartBeatTimeouts.get(otherPeer);
		if (heartBeatTimeout!=null) {
			heartBeatTimeout.cancel();
		}
	}
	private synchronized void rescheduleHeartBeatTimeout(final String otherPeer) {
		cancelHeartBeatTimeout(otherPeer);

		heartBeatTimeouts.put(otherPeer, new Timer());
		heartBeatTimeouts.get(otherPeer).schedule(new TimerTask() {
			@Override
			public void run() {
				FaultDetectionPeer.this.otherPeerCrashed(otherPeer);
			}}, HEART_BEAT_TIMEOUT);
	}

	@Override
	public synchronized boolean heartBeat(String otherPeer) throws RemoteException {
		if (random.nextInt(100) < failurePercentage) {
//			System.out.println(new Date().getSeconds()+" "+
//					"<"+this.name+"> CRASH ");
			FaultDetectionPeer.destroyInstance(this);
			this.mediator.crash();
			return false;
		} else {
			rescheduleHeartBeatTimer(otherPeer);
			rescheduleHeartBeatTimeout(otherPeer);
			return true;
		}
	}

	private synchronized void otherPeerCrashed(String crashedPeer) {
//		System.out.println(new Date().getSeconds()+" "+
//				"<"+this.name+"> REPORT CRASH "+crashedPeer);
		this.removePeer(crashedPeer);
		this.mediator.otherCrashed(crashedPeer);
	}


}
